# -*- coding: utf-8 -*-

__authur__ = 'Yu XH'

import copy
import random
from myAI.hexmap.handle_map import *


class RedOprs:
    class TuJiDui_1:
        Tank, IFV, Infantry, UCV, CM_0, CM_1 = 0, 100, 200, 400, 700, 701
        # 初始位置：Tank: 1832, IFV: 1732, Infantry: 1732, UCV: 1732, CM_0: 1732, CM_1: 1732

    class TuJiDui_2:
        Tank, IFV, Infantry, UCV, CM_0, CM_1 = 1000, 1100, 1200, 1400, 1700, 1701
        # 初始位置：Tank: 2139, IFV: 2039, Infantry: 2039, UCV: 2039, CM_0: 2039, CM_1: 2039

    class TuJiDui_3:
        Tank, IFV, Infantry, UCV, CM_0, CM_1 = 2000, 2100, 2200, 2400, 2700, 2701
        # 初始位置：Tank: 2349, IFV: 2348, Infantry: 2348, UCV: 2348, CM_0: 2348, CM_1: 2348

    class TuJiDui_4:
        Tank, IFV, Infantry, UCV, CM_0, CM_1 = 3000, 3100, 3200, 3400, 3700, 3701
        # 初始位置：Tank: 1953, IFV: 1853, Infantry: 1853, UCV: 1853, CM_0: 1853, CM_1: 1853

    class ZhiShengJi:
        Helicopter_0, Helicopter_1 = 4600, 4601
        # 初始位置：Helicopter_0: 838, Helicopter_1: 1137

    class WuRenJi:
        Drone_0, Drone_1 = 4500, 4501
        # 初始位置：Drone_0: 839, Drone_1: 1138

    class PaoBingQun:
        Artillery_0, Artillery_1, Artillery_2, Artillery_3, Artillery_4, Artillery_5 = 5300, 5301, 5302, 5303, 5304, 5305
        # 初始位置：Artillery_0: 937, Artillery_1: 938, Artillery_2: 939, Artillery_3: 1037, Artillery_4: 1038, Artillery_5: 1039


class BlueOprs:
    class XianQianDui_1:
        Tank_0, Tank_1, IFV_0, IFV_1, Infantry_0, Infantry_1 = 10000, 10001, 10100, 10101, 10200, 10201
        # 初始位置：Tank_0: 6338, Tank_1: 6338, IFV_0: 6337, IFV_1: 6438, Infantry_0: 6337, Infantry_1: 6438

    class XianQianDui_2:
        Tank_0, Tank_1, IFV_0, IFV_1, Infantry_0, Infantry_1 = 11000, 11001, 11100, 11101, 11200, 11201
        # 初始位置：Tank_0: 6446, Tank_1: 6446, IFV_0: 6546, IFV_1: 6647, Infantry_0: 6546, Infantry_1: 6647

    class XianQianDui_3:
        Tank_0, Tank_1, IFV_0, IFV_1, Infantry_0, Infantry_1 = 12000, 12001, 12100, 12101, 12200, 12201
        # 初始位置：Tank_0: 5451, Tank_1: 5451, IFV_0: 5551, IFV_1: 5652, Infantry_0: 5551, Infantry_1: 5652

    class XianQianDui_4:
        Tank_0, Tank_1, IFV_0, IFV_1, Infantry_0, Infantry_1 = 13000, 13001, 13100, 13101, 13200, 13201
        # 初始位置：Tank_0: 5455, Tank_1: 5455, IFV_0: 5555, IFV_1: 5656, Infantry_0: 5555, Infantry_1: 5656

    class ZhiShengJi:
        Helicopter_0, Helicopter_1 = 14600, 14601
        # 初始位置：Helicopter_0: 7337, Helicopter_1: 7637

    class WuRenJi:
        Drone_0, Drone_1 = 14500, 14501
        # 初始位置：Drone_0: 7338, Drone_1: 7638

    class PaoBingQun:
        Artillery_0, Artillery_1, Artillery_2, Artillery_3, Artillery_4, Artillery_5 = 15300, 15301, 15302, 15303, 15304, 15305
        # 初始位置：Artillery_0: 7437, Artillery_1: 7438, Artillery_2: 7439, Artillery_3: 7536, Artillery_4: 7537, Artillery_5: 7538


class FactionAIRed:
    """红方AI"""

    def __init__(self, myAgent):
        self.color = myAgent.color
        self.scenario = myAgent.scenario
        self.max_step = 2880
        self.tactic = None  # 我方策略，'1-attack' | '2-defend'
        self.group_info = {}  # 保存动态分组信息 {group_name: {group_task: ['ToOccupy', city], obj_ids: [opr1, opr2]}}
        self.group_static = {}  # 保存静态分组信息，用于分组动态变化后的回归
        self.init_groups()  # 初始化算子分组，分组结果整场比赛不变化
        self.task = {}  # 与是factionAI的副本，保存算子分配的任务信息{obj_id:['outputfire',param1,param2]}
        self.task_backup = {}  # 用于临时备份某些任务，方便调度
        self.last_observation = myAgent.scenario_info[-1]  # 保存上一步状态，便于和当前状态比较
        self.tan_ke = TanKe(self)
        self.bu_bing = BuBing(self)
        self.zhan_che = ZhanChe(self)
        self.pao_bing = PaoBing(self)
        self.zhi_sheng_ji = ZhiShengJi(self)
        self.wu_ren_ji = WuRenJi(self)
        self.xun_fei_dan = XunFeiDan(self)

    def update(self, factionAI, myAgent):
        myAgent.agenda.ipcArgs.mutex_task.acquire()
        self.task = copy.deepcopy(factionAI.task)
        myAgent.agenda.ipcArgs.mutex_task.release()

        our_oprs = myAgent.get_our_oprs()

        # 没有可以使用的算子
        if not our_oprs:
            self.task = {}
            return

        # 如果我方有算子死亡，则将其任务删除
        our_oprs_id = [opr['obj_id'] for opr in our_oprs]
        need_pop = [opr_id for opr_id in self.task.keys() if opr_id not in our_oprs_id]
        for pop_id in need_pop:
            self.task.pop(pop_id)

        # 如果我方算子有无效任务（Success, Failure, Error等），将任务删除
        for opr in our_oprs:
            opr_id = opr['obj_id']
            if opr_id in self.task.keys():
                if self.task[opr_id][0] in ['Success', 'Failure', 'Error']:
                    self.task.pop(opr_id)

        # 将死亡算子的信息从group_info中移除
        self.remove_group_dead_members(myAgent)

        # 对我方算子进行重分组 -- 分组策略
        self.regroup_oprs(myAgent)

        # 分组任务分派
        self.tan_ke.plan(self, myAgent)
        self.bu_bing.plan(self, myAgent)
        self.zhan_che.plan(self, myAgent)
        self.zhi_sheng_ji.plan(self, myAgent)
        self.wu_ren_ji.plan(self, myAgent)
        self.xun_fei_dan.plan(self, myAgent)

        # 跨组任务分派
        if self.check_control(myAgent):
            self.plan_control(myAgent)
        self.patrol_towns(myAgent)

        # 炮兵任务
        self.pao_bing.plan(self, myAgent)

        # 应急响应
        self.plan_launch_cm(myAgent)
        self.control_neighbor(myAgent)
        self.hide_drone(myAgent)
        self.hide_helicopter(myAgent)

    def init_groups(self):
        self.group_static['pao_bing'] = [RedOprs.PaoBingQun.Artillery_0, RedOprs.PaoBingQun.Artillery_1,
                                         RedOprs.PaoBingQun.Artillery_2, RedOprs.PaoBingQun.Artillery_3,
                                         RedOprs.PaoBingQun.Artillery_4, RedOprs.PaoBingQun.Artillery_5]
        self.group_info['pao_bing'] = {'group_task': [], 'obj_ids': self.group_static['pao_bing']}
        self.group_static['zhi_sheng_ji'] = [RedOprs.ZhiShengJi.Helicopter_0, RedOprs.ZhiShengJi.Helicopter_1]
        self.group_info['zhi_sheng_ji'] = {'group_task': [], 'obj_ids': self.group_static['zhi_sheng_ji']}
        self.group_static['wu_ren_ji'] = [RedOprs.WuRenJi.Drone_0, RedOprs.WuRenJi.Drone_1]
        self.group_info['wu_ren_ji'] = {'group_task': [], 'obj_ids': self.group_static['wu_ren_ji']}
        self.group_static['xun_fei_dan'] = [RedOprs.TuJiDui_1.CM_0, RedOprs.TuJiDui_1.CM_1,
                                            RedOprs.TuJiDui_2.CM_0, RedOprs.TuJiDui_2.CM_1,
                                            RedOprs.TuJiDui_3.CM_0, RedOprs.TuJiDui_3.CM_1,
                                            RedOprs.TuJiDui_4.CM_0, RedOprs.TuJiDui_4.CM_1]
        self.group_info['xun_fei_dan'] = {'group_task': [], 'obj_ids': []}
        self.group_static['tan_ke'] = [RedOprs.TuJiDui_1.Tank, RedOprs.TuJiDui_2.Tank,
                                       RedOprs.TuJiDui_3.Tank, RedOprs.TuJiDui_4.Tank]
        self.group_info['tan_ke'] = {'group_task': [], 'obj_ids': self.group_static['tan_ke']}
        self.group_static['bu_bing'] = [RedOprs.TuJiDui_1.Infantry, RedOprs.TuJiDui_2.Infantry,
                                        RedOprs.TuJiDui_3.Infantry, RedOprs.TuJiDui_4.Infantry]
        self.group_info['bu_bing'] = {'group_task': [], 'obj_ids': self.group_static['bu_bing']}
        self.group_static['zhan_che'] = [RedOprs.TuJiDui_1.IFV, RedOprs.TuJiDui_2.IFV,
                                         RedOprs.TuJiDui_3.IFV, RedOprs.TuJiDui_4.IFV,
                                         RedOprs.TuJiDui_1.UCV, RedOprs.TuJiDui_2.UCV,
                                         RedOprs.TuJiDui_3.UCV, RedOprs.TuJiDui_4.UCV]
        self.group_info['zhan_che'] = {'group_task': [], 'obj_ids': self.group_static['zhan_che']}

    def remove_group_dead_members(self, myAgent):
        """删除group_info中的已死亡算子，group_static不变化"""
        for obj_id in self.group_info['pao_bing']['obj_ids']:
            if not myAgent.get_bop(obj_id):
                self.group_info['pao_bing']['obj_ids'].remove(obj_id)
        for obj_id in self.group_info['zhi_sheng_ji']['obj_ids']:
            if not myAgent.get_bop(obj_id):
                self.group_info['zhi_sheng_ji']['obj_ids'].remove(obj_id)
        for obj_id in self.group_info['wu_ren_ji']['obj_ids']:
            if not myAgent.get_bop(obj_id):
                self.group_info['wu_ren_ji']['obj_ids'].remove(obj_id)
        for obj_id in self.group_info['xun_fei_dan']['obj_ids']:
            if not myAgent.get_bop(obj_id):
                self.group_info['xun_fei_dan']['obj_ids'].remove(obj_id)
        for obj_id in self.group_info['tan_ke']['obj_ids']:
            if not myAgent.get_bop(obj_id):
                self.group_info['tan_ke']['obj_ids'].remove(obj_id)
        for obj_id in self.group_info['bu_bing']['obj_ids']:
            if not myAgent.get_bop(obj_id):
                self.group_info['bu_bing']['obj_ids'].remove(obj_id)
        for obj_id in self.group_info['zhan_che']['obj_ids']:
            if not myAgent.get_bop(obj_id):
                self.group_info['zhan_che']['obj_ids'].remove(obj_id)

    def regroup_oprs(self, myAgent):
        """重新分组"""
        """=== 空闲算子按照group_static进行分组 ==="""
        our_oprs = myAgent.get_our_oprs()
        for opr in our_oprs:
            opr_id = opr['obj_id']
            regroup_flag = False
            if opr_id in self.task.keys():
                if self.task[opr_id][0] in ['Success', 'Failure', 'Error'] and opr['on_board'] == 0:
                    regroup_flag = True
            else:
                if opr['on_board'] == 0:
                    regroup_flag = True
            if regroup_flag:
                group_name = None
                for name, obj_ids in self.group_static.items():
                    if opr_id in obj_ids:
                        group_name = name
                if group_name:
                    if opr_id not in self.group_info[group_name]['obj_ids']:
                        self.group_info[group_name]['obj_ids'].append(opr_id)
                if self.task_backup.get(opr_id):  # 如果空闲算子在self.task_backup中有备份，继续备份中的任务
                    self.task[opr_id] = self.task_backup.pop(opr_id)

        """=== 调整分组 ==="""
        # 暂无需要调整分组的情形

    def check_control(self, myAgent):
        if myAgent.observation['time']['cur_step'] <= 1500:
            return False
        our_tanks = []
        for opr in myAgent.get_our_oprs():
            if opr['sub_type'] == BopType.Tank:
                our_tanks.append(opr)
        other_cities = []
        for city in myAgent.observation['cities']:
            if city['flag'] != self.color:
                flag_enemy_around = False
                for enemy_info in myAgent.situations.enemies:
                    if not enemy_info['spread_flag']:
                        continue
                    enemy = enemy_info['operator']
                    if enemy['type'] == BopType.Aircraft:
                        continue
                    if cal_distance(enemy['cur_hex'], city['coord']) <= 1:
                        flag_enemy_around = True
                        break
                if not flag_enemy_around:
                    other_cities.append(city)
        if not other_cities:
            return False
        if myAgent.observation['time']['cur_step'] <= 2500:
            if not our_tanks:
                return False
            n_tanks_enemy = 0
            for enemy_info in myAgent.situations.enemies:
                enemy = enemy_info['operator']
                if enemy['sub_type'] == BopType.Tank:
                    n_tanks_enemy += 1
            if n_tanks_enemy > 1:
                return False
        return True

    def plan_control(self, myAgent):
        our_tanks = []
        for opr in myAgent.get_our_oprs():
            if opr['sub_type'] == BopType.Tank:
                our_tanks.append(opr)
        other_cities = []
        for city in myAgent.observation['cities']:
            if city['flag'] != self.color:
                flag_enemy_around = False
                for enemy_info in myAgent.situations.enemies:
                    if not enemy_info['spread_flag']:
                        continue
                    enemy = enemy_info['operator']
                    if enemy['type'] == BopType.Aircraft:
                        continue
                    if cal_distance(enemy['cur_hex'], city['coord']) <= 1:
                        flag_enemy_around = True
                        break
                if not flag_enemy_around:
                    other_cities.append(city)
        if our_tanks:
            control_candidates = our_tanks
        else:
            control_candidates = []
            for opr in myAgent.get_our_oprs():
                if opr['type'] == BopType.Vehicle and opr['sub_type'] != BopType.Artillery:
                    control_candidates.append(opr)
        min_dist = 1000
        best_opr, best_city = None, None
        for opr in control_candidates:
            for city in other_cities:
                cur_dist = cal_distance(city['coord'], opr['cur_hex'])
                if min_dist > cur_dist:
                    best_opr = opr
                    best_city = city
                    min_dist = cur_dist
        if best_opr and best_city:
            self.task[best_opr['obj_id']] = ['control', best_city['coord']]
            # 为非夺控算子分派任务
            assult_oprs = []
            for opr in myAgent.get_our_oprs():
                if 'control' in self.task.get(opr['obj_id'], []):
                    continue
                if opr['sub_type'] == BopType.UCV:
                    assult_oprs.append(opr)
                elif opr['sub_type'] == BopType.IFV:
                    assult_oprs.append(opr)
                elif opr['sub_type'] == BopType.Tank:
                    assult_oprs.append(opr)
            enemies_around_city = []
            for enemy_info in myAgent.situations.enemies:
                if not enemy_info['spread_flag']:
                    continue
                enemy = enemy_info['operator']
                dist_cities = cal_distance(enemy['cur_hex'], best_city['coord'])
                if dist_cities <= 3:
                    if enemy['type'] != BopType.Aircraft:
                        enemies_around_city.append(enemy)
            if enemies_around_city:
                for opr in assult_oprs:
                    self.task[opr['obj_id']] = ['assult', [enemy['obj_id'] for enemy in enemies_around_city]]
            else:
                for opr in assult_oprs:
                    if opr['sub_type'] == BopType.Tank:
                        if self.task.get(opr['obj_id']):
                            if 'ambush4th' in self.task[opr['obj_id']] and cal_distance(self.task[opr['obj_id']][1],
                                                                                        best_city['coord']) <= 3:
                                continue
                        self.task[opr['obj_id']] = ['ambush4th', random.choice(get_range(best_city['coord'], 3, myAgent.get_map_size())), False]
                    else:
                        self.task[opr['obj_id']] = ['output_firepower', get_range(best_city['coord'], 3, myAgent.get_map_size())]
        # 为步兵算子分派任务
        for opr in myAgent.get_our_oprs():
            if opr['type'] == BopType.Infantry:
                if min([cal_distance(opr['cur_hex'], city['coord']) for city in myAgent.observation['cities']]) <= 1:
                    self.task[opr['obj_id']] = ['guard2nd', True]
                else:
                    cloest_city = min(myAgent.observation['cities'],
                                      key=lambda city: cal_distance(opr['cur_hex'], city['coord']))
                    self.task[opr['obj_id']] = ['maneuver', get_line(cloest_city['coord'], opr['cur_hex'])[0]]

    def patrol_towns(self, myAgent):
        patrol_area = [3739, 3838, 3839]
        city_locs = [3640, 3837, 3939]
        tar_cities = []
        for city_loc in city_locs:
            city = myAgent.get_city(city_loc)
            if city['flag'] == self.color:
                continue
            flag_enemy_by_city = False
            for enemy_info in myAgent.situations.enemies:
                if not enemy_info['spread_flag']:
                    continue
                if enemy_info['operator']['type'] == BopType.Aircraft:
                    continue
                enemy_loc = enemy_info['operator']['cur_hex']
                if cal_distance(enemy_loc, city_loc) <= 1:
                    flag_enemy_by_city = True
                    break
            if not flag_enemy_by_city:
                tar_cities.append(city)
        patrol_infs = []
        for opr in myAgent.get_our_oprs():
            if opr['type'] == BopType.Infantry:
                if min([cal_distance(opr['cur_hex'], hex) for hex in patrol_area]) <= 1:
                    patrol_infs.append(opr)
        if patrol_infs:
            for infantry in patrol_infs:
                if infantry['stop'] == 1:
                    tar_area = []
                    if tar_cities:
                        for hex in patrol_area:
                            if min([cal_distance(hex, city['coord']) for city in tar_cities]) <= 1:
                                tar_area.append(hex)
                    if not tar_area:
                        tar_area = patrol_area
                    tar_loc = random.choice(tar_area)
                    self.task[infantry['obj_id']] = ['ambush4th', tar_loc, True]

    def control_neighbor(self, myAgent):
        """算子处于邻格时，快速夺控"""
        enemies = myAgent.situations.enemies
        for opr in myAgent.get_our_oprs():
            if opr['type'] == BopType.Aircraft:
                continue
            if opr['type'] == BopType.Artillery:
                continue
            opr_loc = opr['cur_hex']
            for city in myAgent.observation['cities']:
                city_loc = city['coord']
                if city['flag'] == self.color:
                    continue
                if cal_distance(opr_loc, city_loc) > 1:
                    continue
                flag_enemy_by_city = False
                for enemy_info in enemies:
                    if not enemy_info['spread_flag']:
                        continue
                    if enemy_info['operator']['type'] == BopType.Aircraft:
                        continue
                    enemy_loc = enemy_info['operator']['cur_hex']
                    if cal_distance(enemy_loc, city_loc) <= 1:
                        flag_enemy_by_city = True
                        break
                if not flag_enemy_by_city:
                    if self.task.get(opr['obj_id']):
                        if 'try_control' not in self.task[opr['obj_id']] and not self.task_backup.get(
                                opr['obj_id']):
                            self.task_backup[opr['obj_id']] = self.task.pop(opr['obj_id'])
                    self.task[opr['obj_id']] = ['try_control', city_loc]

    def hide_helicopter(self, myAgent):
        # 遭到直升机突袭的反应策略（不考虑车辆的反应）
        map_size = myAgent.get_map_size()
        n_hexes = map_size['row'] * map_size['col']
        dist_matrix = myAgent.my_map['dist_matrix']
        see_matrix = myAgent.my_map['see_matrix'][2]
        enemy_heli_0 = myAgent.get_bop(BlueOprs.ZhiShengJi.Helicopter_0)
        enemy_heli_1 = myAgent.get_bop(BlueOprs.ZhiShengJi.Helicopter_1)
        if not enemy_heli_0 and not enemy_heli_1:
            return
        enemy_helis = []
        if enemy_heli_0:
            enemy_helis.append(enemy_heli_0)
        if enemy_heli_1:
            enemy_helis.append(enemy_heli_1)
        # 计算直升机的威胁范围
        # threat_area_vehicle_bool = np.zeros(n_hexes, dtype=bool)
        shoot_area_infantry_bool = np.zeros(n_hexes, dtype=bool)
        for heli in enemy_helis:
            # heli_info = myAgent.get_enemy_info(heli['obj_id'])
            heli_loc_ind = mapid_2_matind(heli['cur_hex'], map_size)
            # threat_area_vehicle_bool = np.logical_or(threat_area_vehicle_bool,
            #                                          get_opr_shoot_range(heli, BopType.Vehicle, myAgent,
            #                                                              heli['carry_weapon_ids'],
            #                                                              heli_info['remain_bullet_nums']))
            shoot_area_infantry_bool = np.logical_or(
                shoot_area_infantry_bool, np.logical_and(dist_matrix[heli_loc_ind] <= 10, see_matrix[heli_loc_ind]))
        # threat_area_vehicle = list(matind_2_mapid(np.arange(n_hexes)[threat_area_vehicle_bool], map_size))
        shoot_area_infantry = list(matind_2_mapid(np.arange(n_hexes)[shoot_area_infantry_bool], map_size))
        for opr in myAgent.get_our_oprs():
            if opr['type'] == BopType.Infantry:
                """步兵的反应：停下来准备射击"""
                if opr['remain_bullet_nums'][BulletType.HeavyMissile] <= 0:
                    continue
                if opr['cur_hex'] not in shoot_area_infantry:
                    continue
                ambush_loc = opr['move_path'][-1] if opr['move_path'] else opr['cur_hex']
                if self.task.get(opr['obj_id']):
                    if 'air_defense' not in self.task[opr['obj_id']] and not self.task_backup.get(
                            opr['obj_id']):  # 本策略中'air_defense'的任务只用于步兵打直升机
                        self.task_backup[opr['obj_id']] = self.task.pop(opr['obj_id'])
                self.task[opr['obj_id']] = ['air_defense', ambush_loc]
            # elif opr['type'] == BopType.Vehicle:
            #     """车辆的反应：能逃就逃"""
            #     if opr['cur_hex'] not in threat_area_vehicle:
            #         continue
            #     if self.task.get(opr['obj_id']):
            #         if 'hide' not in self.task[opr['obj_id']] and not self.task_backup.get(opr['obj_id']):
            #             self.task_backup[opr['obj_id']] = self.task.pop(opr['obj_id'])
            #         self.task[opr['obj_id']] = ['hide']

    def hide_drone(self, myAgent):
        enemy_drones = [myAgent.get_bop(BlueOprs.WuRenJi.Drone_0), myAgent.get_bop(BlueOprs.WuRenJi.Drone_1)]
        survive_drones = [drone for drone in enemy_drones if drone]
        for opr in myAgent.get_our_oprs():
            if opr['sub_type'] == BopType.IFV and opr['stop'] == 1:  # 战车逃离无人机视野
                flag_drone_threat = False
                for enemy_drone in enemy_drones:
                    if not enemy_drone:
                        continue
                    if cal_distance(enemy_drone['cur_hex'], opr['cur_hex']) <= 1:
                        drone_info = myAgent.get_enemy_info(enemy_drone['obj_id'])
                        if not drone_info['trace']:
                            continue
                        if drone_info['trace'][0][0] != enemy_drone['cur_hex']:
                            continue
                        arrive_step = drone_info['trace'][0][1]
                        cur_step = myAgent.observation['time']['cur_step']
                        if cur_step - arrive_step > 20:
                            flag_drone_threat = True
                if not flag_drone_threat:
                    continue
                flag_near_infantry = False
                if self.group_info['bu_bing']['obj_ids']:
                    closest_inf_id = min(self.group_info['bu_bing']['obj_ids'], key=lambda inf_id:
                                           cal_distance(opr['cur_hex'], myAgent.get_bop(inf_id)['cur_hex']))
                    closest_infantry = myAgent.get_bop(closest_inf_id)
                    if cal_distance(opr['cur_hex'], closest_infantry['cur_hex']) <= 3:
                        flag_near_infantry = True
                hide_area = []
                for hex in get_range(opr['cur_hex'], 3, myAgent.get_map_size()):
                    terrain_type = get_terrain_info(myAgent.map.get_map_data(), hex)
                    if terrain_type in [TerrainType.Forest, TerrainType.Resident]:
                        radius = 1
                        if min([cal_distance(hex, drone['cur_hex']) for drone in survive_drones]) > radius:
                            hide_area.append(hex)
                if not hide_area:
                    continue
                if flag_near_infantry:
                    hide_loc = min(hide_area, key=lambda hex: cal_distance(hex, opr['cur_hex']) +
                                                              cal_distance(hex, closest_infantry['cur_hex']))
                else:
                    hide_loc = min(hide_area, key=lambda hex: cal_distance(hex, opr['cur_hex']))
                if self.task.get(opr['obj_id']):
                    if 'maneuver' not in self.task[opr['obj_id']] and not self.task_backup.get(opr['obj_id']):
                        self.task_backup[opr['obj_id']] = self.task.pop(opr['obj_id'])
                self.task[opr['obj_id']] = ['maneuver', hide_loc]
                # print(f"\t躲避无人机：hide_loc: {hide_loc}, 无人机位置: {survive_drones[0]['cur_hex']}, 战车位置: {opr['cur_hex']}, hide_area: {hide_area}")
            if opr['type'] == BopType.Infantry:  # 步兵对无人机打击
                if opr['remain_bullet_nums'][BulletType.HeavyMissile] <= 0:
                    continue
                flag_drone_in_sight = False
                for enemy_drone in enemy_drones:
                    if not enemy_drone:
                        continue
                    if cal_distance(enemy_drone['cur_hex'], opr['cur_hex']) <= 1:
                        flag_drone_in_sight = True
                if not flag_drone_in_sight:
                    continue
                ambush_loc = opr['move_path'][-1] if opr['move_path'] else opr['cur_hex']
                if self.task.get(opr['obj_id']):
                    if 'air_defense' not in self.task[opr['obj_id']] and not self.task_backup.get(opr['obj_id']):
                        self.task_backup[opr['obj_id']] = self.task.pop(opr['obj_id'])
                self.task[opr['obj_id']] = ['air_defense', ambush_loc]

    def plan_launch_cm(self, myAgent):
        # === 安排巡飞弹的下车任务 ===
        if myAgent.observation['time']['cur_step'] <= 1000:
            # 确保首次发射不出问题
            return
        first_choice, second_choice = None, None
        our_oprs = myAgent.get_our_oprs()
        for opr in our_oprs:
            if opr['sub_type'] == BopType.CruiseMissile:
                if opr['on_board'] == 1 and opr['get_off_remain_time'] <= 0:
                    if self.cm_getoff_check(opr['obj_id'], myAgent):
                        ifv_id = opr['car']
                        # 车上有小兵，不发射巡飞弹
                        ifv = myAgent.get_bop(ifv_id)
                        if not ifv:
                            continue
                        flag_infantry = False
                        for opr_id in ifv['passenger_ids']:
                            opr = myAgent.get_bop(opr_id)
                            if not opr:
                                continue
                            if opr['type'] == BopType.Infantry:
                                flag_infantry = True
                                break
                        if flag_infantry:
                            continue
                        if ifv_id in myAgent.observation['valid_actions'].keys():
                            if ActionType.GetOff in myAgent.observation['valid_actions'][ifv_id].keys():
                                first_choice = ifv_id
                                break
                        if not second_choice:
                            second_choice = ifv_id
        launcher_id = first_choice if first_choice else second_choice
        if launcher_id:
            cm_id = None
            for obj_id in myAgent.get_bop(launcher_id)['passenger_ids']:
                if myAgent.get_bop(obj_id)['sub_type'] == BopType.CruiseMissile:
                    cm_id = obj_id
                    break
            if cm_id:
                group_name = get_group_by_obj_id(launcher_id, self)
                if group_name:
                    self.group_info[group_name]['obj_ids'].remove(launcher_id)  # 暂时将战车算子调离原分组
                    if self.task.get(launcher_id) and not self.task_backup.get(launcher_id):
                        self.task_backup[launcher_id] = self.task.pop(launcher_id)
                self.task[launcher_id] = ['unload', cm_id, True]

    def cm_getoff_check(self, obj_id, myAgent):
        """
        确定当前巡飞弹是否可以下车
        """
        # 下车条件：找出巡飞弹的装载车辆veh，如果当前天上没有veh发射的其它巡飞弹，即可下车
        veh_id = myAgent.get_bop(obj_id)['car']
        for opr in myAgent.observation['operators']:
            if opr['sub_type'] == BopType.CruiseMissile and opr['launcher'] == veh_id:
                return False
        return True

    def check_enter_firepower_phase(self, myAgent):
        """判断红方地面组是否执行夺控任务"""
        if myAgent.observation['time']['cur_step'] >= 1500:  # 留1000s用于夺控
            return True
        no_vehicles_enemy = True
        for enemy_info in myAgent.situations.enemies:
            enemy = enemy_info['operator']
            if enemy['type'] == BopType.Vehicle and enemy['sub_type'] != BopType.Artillery:
                no_vehicles_enemy = False
                break
        if no_vehicles_enemy:  # 敌方没有车辆，实施夺控
            return True
        missile_count = 0
        for opr in myAgent.get_our_oprs():
            if opr['sub_type'] == BopType.IFV:
                missile_count += opr['remain_bullet_nums'][BulletType.HeavyMissile]
        if missile_count <= 0:  # 我方战车导弹耗尽，实施夺控
            return True
        return False


class TanKe:
    """坦克组的策略实现"""

    def __init__(self, factionAI):
        self.members = factionAI.group_static['tan_ke']
        self.group_task = ['Deployment']
        self.tactic = None
        self.cur_step = 0

    def plan(self, factionAI, myAgent):
        self.cur_step = myAgent.observation['time']['cur_step']
        self.members = factionAI.group_info['tan_ke']['obj_ids']
        if not self.members:
            return

        # === 更新组任务 ===
        self.update_group_task(factionAI, myAgent)

        # === 分派算子任务 ===
        self.assign_opr_tasks(factionAI, myAgent)

    def update_group_task(self, factionAI, myAgent):
        if 'Deployment' in self.group_task:
            if self.cur_step <= 1100:
                threshold = 3500
            else:
                threshold = 3800
            n_assult_tanks = 0
            for enemy_info in myAgent.situations.enemies:
                if not enemy_info['spread_flag']:
                    continue
                enemy = enemy_info['operator']
                if enemy['sub_type'] == BopType.Tank and enemy['cur_hex'] < threshold:
                    n_assult_tanks += 1
            if n_assult_tanks >= 2:  # 有2辆以上的坦克入侵，我方防守
                self.group_task = ['Defend']

    def assign_opr_tasks(self, factionAI, myAgent):
        tank_1, tank_2 = myAgent.get_bop(RedOprs.TuJiDui_1.Tank), myAgent.get_bop(RedOprs.TuJiDui_2.Tank)
        tank_3, tank_4 = myAgent.get_bop(RedOprs.TuJiDui_3.Tank), myAgent.get_bop(RedOprs.TuJiDui_4.Tank)

        if 'Deployment' in self.group_task:
            """=== 部署任务 ==="""
            if tank_1:
                if tank_1['cur_hex'] == 1832:
                    factionAI.task[tank_1['obj_id']] = ['maneuver', 2842]
                if tank_1['cur_hex'] == 2842:
                    factionAI.task[tank_1['obj_id']] = ['ambush4th', 2945, True]
                if tank_1['cur_hex'] == 2945 and self.cur_step > 1000:
                    factionAI.task[tank_1['obj_id']] = ['ambush4th', 3343, True]
            if tank_2:
                if tank_2['cur_hex'] == 2139:
                    factionAI.task[tank_2['obj_id']] = ['maneuver', 2842]
                if tank_2['cur_hex'] == 2842:
                    factionAI.task[tank_2['obj_id']] = ['ambush4th', 2945, True]
                if tank_2['cur_hex'] == 2945 and self.cur_step > 1000:
                    factionAI.task[tank_2['obj_id']] = ['ambush4th', 3344, True]
            if tank_3:
                if tank_3['cur_hex'] == 2349:
                    factionAI.task[tank_3['obj_id']] = ['ambush4th', 3045, True]
                if tank_3['cur_hex'] == 3045 and self.cur_step > 1000:
                    factionAI.task[tank_3['obj_id']] = ['ambush4th', 3445, True]
            if tank_4:
                if tank_4['cur_hex'] == 1953:
                    factionAI.task[tank_4['obj_id']] = ['ambush4th', 3145, True]
                if tank_4['cur_hex'] == 3145 and self.cur_step > 1000:
                    factionAI.task[tank_4['obj_id']] = ['ambush4th', 3446, True]
        elif 'Defend' in self.group_task:
            """=== 防守任务 ==="""
            enemy_tanks, enemy_ifvs = [], []
            for enemy_info in myAgent.situations.enemies:
                if not enemy_info['spread_flag']:
                    continue
                enemy = enemy_info['operator']
                if enemy['sub_type'] == BopType.Tank and enemy['cur_hex'] < 4100:
                    enemy_tanks.append(enemy['obj_id'])
                elif enemy['sub_type'] == BopType.IFV and enemy['cur_hex'] < 4100:
                    enemy_ifvs.append(enemy['obj_id'])
            if enemy_tanks:
                for tank in [tank_1, tank_2, tank_3, tank_4]:
                    if not tank:
                        continue
                    factionAI.task[tank['obj_id']] = ['assult', enemy_tanks]
            elif enemy_ifvs:
                for tank in [tank_1, tank_2, tank_3, tank_4]:
                    if not tank:
                        continue
                    factionAI.task[tank['obj_id']] = ['assult', enemy_ifvs]


class BuBing:
    """步兵组的策略实现"""

    def __init__(self, factionAI):
        self.members = factionAI.group_static['bu_bing']
        self.group_task = ['Deployment']
        self.tactic = None
        self.cur_step = 0

    def plan(self, factionAI, myAgent):
        self.cur_step = myAgent.observation['time']['cur_step']
        self.members = factionAI.group_info['bu_bing']['obj_ids']
        if not self.members:
            return

        # === 更新组任务 ===
        self.update_group_task(factionAI, myAgent)

        # === 分派算子任务 ===
        self.assign_opr_tasks(factionAI, myAgent)

    def update_group_task(self, factionAI, myAgent):
        pass

    def assign_opr_tasks(self, factionAI, myAgent):
        infantry_1, infantry_2 = myAgent.get_bop(RedOprs.TuJiDui_1.Infantry), myAgent.get_bop(
            RedOprs.TuJiDui_2.Infantry)
        infantry_3, infantry_4 = myAgent.get_bop(RedOprs.TuJiDui_3.Infantry), myAgent.get_bop(
            RedOprs.TuJiDui_4.Infantry)

        if 'Deployment' in self.group_task:
            """=== 部署任务 ==="""
            if infantry_1:
                if infantry_1['cur_hex'] == 1732:
                    factionAI.task[infantry_1['obj_id']] = ['maneuver', 1740]
                if infantry_1['cur_hex'] == 1740:
                    factionAI.task[infantry_1['obj_id']] = ['maneuver', 2645]
                if infantry_1['cur_hex'] == 2645:
                    factionAI.task[infantry_1['obj_id']] = ['guard2nd', True]
            if infantry_2:
                if infantry_2['cur_hex'] == 2039:
                    factionAI.task[infantry_2['obj_id']] = ['maneuver', 2536]
                if infantry_2['cur_hex'] == 2536:
                    factionAI.task[infantry_2['obj_id']] = ['maneuver', 3640]
                if infantry_2['cur_hex'] == 3640:
                    factionAI.task[infantry_2['obj_id']] = ['maneuver', 3739]
                if infantry_2['cur_hex'] == 3739:
                    factionAI.task[infantry_2['obj_id']] = ['guard2nd', True]
            if infantry_3:
                if infantry_3['cur_hex'] == 2348:
                    factionAI.task[infantry_3['obj_id']] = ['maneuver', 3144]
                if infantry_3['cur_hex'] == 3144:
                    factionAI.task[infantry_3['obj_id']] = ['maneuver', 3747]
                if infantry_3['cur_hex'] == 3747:
                    factionAI.task[infantry_3['obj_id']] = ['maneuver', 3846]
                if infantry_3['cur_hex'] == 3846:
                    factionAI.task[infantry_3['obj_id']] = ['guard2nd', True]
            if infantry_4:
                if infantry_4['get_off_remain_time'] > 0:
                    factionAI.task[infantry_4['obj_id']] = ['maneuver', 3947]
                if infantry_4['cur_hex'] == 3947:
                    factionAI.task[infantry_4['obj_id']] = ['guard2nd', True]


class ZhanChe:
    """战车组的策略实现"""

    def __init__(self, factionAI):
        self.members = factionAI.group_static['zhan_che']
        self.group_task = ['Deployment']
        self.tactic = None
        self.cur_step = 0

    def plan(self, factionAI, myAgent):
        self.cur_step = myAgent.observation['time']['cur_step']
        self.members = factionAI.group_info['zhan_che']['obj_ids']
        if not self.members:
            return

        # === 更新组任务 ===
        self.update_group_task(factionAI, myAgent)

        # === 分派算子任务 ===
        self.assign_opr_tasks(factionAI, myAgent)

    def update_group_task(self, factionAI, myAgent):
        pass

    def assign_opr_tasks(self, factionAI, myAgent):
        ifv_1, ifv_2 = myAgent.get_bop(RedOprs.TuJiDui_1.IFV), myAgent.get_bop(RedOprs.TuJiDui_2.IFV)
        ifv_3, ifv_4 = myAgent.get_bop(RedOprs.TuJiDui_3.IFV), myAgent.get_bop(RedOprs.TuJiDui_4.IFV)
        ucv_1, ucv_2 = myAgent.get_bop(RedOprs.TuJiDui_1.UCV), myAgent.get_bop(RedOprs.TuJiDui_2.UCV)
        ucv_3, ucv_4 = myAgent.get_bop(RedOprs.TuJiDui_3.UCV), myAgent.get_bop(RedOprs.TuJiDui_4.UCV)

        if 'Deployment' in self.group_task:
            """=== 部署任务 ==="""
            if ifv_1:
                if ifv_1['cur_hex'] == 1732:
                    factionAI.task[ifv_1['obj_id']] = ['maneuver', 2038]
                if ifv_1['cur_hex'] == 2038:
                    cm_0_1 = myAgent.get_bop(RedOprs.TuJiDui_1.CM_0)
                    if cm_0_1:
                        if cm_0_1['on_board'] == 1 and cm_0_1['get_off_remain_time'] <= 0:
                            factionAI.task[ifv_1['obj_id']] = ['unload', RedOprs.TuJiDui_1.CM_0, False]
                    if self.cur_step > 450:
                        # 判断是否有直升机的潜在威胁
                        blue_heli_0 = myAgent.get_bop(BlueOprs.ZhiShengJi.Helicopter_0)
                        blue_heli_1 = myAgent.get_bop(BlueOprs.ZhiShengJi.Helicopter_1)
                        flag_heli_threat = False
                        if blue_heli_0:
                            if blue_heli_0['stop'] == 1 and cal_distance(blue_heli_0['cur_hex'], 2137) <= 20:
                                flag_heli_threat = True
                        if blue_heli_1:
                            if blue_heli_1['stop'] == 1 and cal_distance(blue_heli_1['cur_hex'], 2137) <= 20:
                                flag_heli_threat = True
                        if not flag_heli_threat:
                            factionAI.task[ifv_1['obj_id']] = ['maneuver', 2237]
                if ifv_1['cur_hex'] == 2237:
                    if self.cur_step > 550:
                        factionAI.task[ifv_1['obj_id']] = ['maneuver', 2337]
                if ifv_1['cur_hex'] == 2337:
                    # 判断是否有直升机的潜在威胁
                    blue_heli_0 = myAgent.get_bop(BlueOprs.ZhiShengJi.Helicopter_0)
                    blue_heli_1 = myAgent.get_bop(BlueOprs.ZhiShengJi.Helicopter_1)
                    flag_heli_threat = False
                    if blue_heli_0:
                        if blue_heli_0['stop'] == 1 and cal_distance(blue_heli_0['cur_hex'], 2438) <= 20:
                            flag_heli_threat = True
                    if blue_heli_1:
                        if blue_heli_1['stop'] == 1 and cal_distance(blue_heli_1['cur_hex'], 2438) <= 20:
                            flag_heli_threat = True
                    if not flag_heli_threat:
                        factionAI.task[ifv_1['obj_id']] = ['maneuver', 2539]
                if ifv_1['cur_hex'] == 2539:
                    # 判断是否有直升机的潜在威胁
                    blue_heli_0 = myAgent.get_bop(BlueOprs.ZhiShengJi.Helicopter_0)
                    blue_heli_1 = myAgent.get_bop(BlueOprs.ZhiShengJi.Helicopter_1)
                    flag_heli_threat = False
                    if blue_heli_0:
                        if blue_heli_0['stop'] == 1 and cal_distance(blue_heli_0['cur_hex'], 2640) <= 20:
                            flag_heli_threat = True
                    if blue_heli_1:
                        if blue_heli_1['stop'] == 1 and cal_distance(blue_heli_1['cur_hex'], 2640) <= 20:
                            flag_heli_threat = True
                    if not flag_heli_threat:
                        factionAI.task[ifv_1['obj_id']] = ['maneuver', 2641]
                if ifv_1['cur_hex'] == 2641:
                    factionAI.task[ifv_1['obj_id']] = ['maneuver', 2942]
                if ifv_1['cur_hex'] == 2942:
                    factionAI.task[ifv_1['obj_id']] = ['maneuver', 2945]
                if ifv_1['cur_hex'] == 2945:
                    factionAI.task[ifv_1['obj_id']] = ['guard2nd', True]
            if ifv_2:
                if ifv_2['cur_hex'] == 2039:
                    factionAI.task[ifv_2['obj_id']] = ['maneuver', 2642]
                if ifv_2['cur_hex'] == 2642:
                    factionAI.task[ifv_2['obj_id']] = ['maneuver', [2741, 2842]]
                if ifv_2['cur_hex'] == 2842:
                    cm_0_2 = myAgent.get_bop(RedOprs.TuJiDui_2.CM_0)
                    if cm_0_2:
                        if cm_0_2['on_board'] == 1 and cm_0_2['get_off_remain_time'] <= 0:
                            factionAI.task[ifv_2['obj_id']] = ['unload', RedOprs.TuJiDui_2.CM_0, False]
                    if cm_0_2['get_off_remain_time'] > 0:
                        factionAI.task[ifv_2['obj_id']] = ['guard2nd', True]
                if 900 < self.cur_step <= 950:
                    factionAI.task[ifv_2['obj_id']] = ['maneuver', 3045]
                if ifv_2['cur_hex'] == 3045:
                    factionAI.task[ifv_2['obj_id']] = ['guard2nd', True]
            if ifv_3:
                cm_0_3 = myAgent.get_bop(RedOprs.TuJiDui_3.CM_0)
                if cm_0_3:
                    if ifv_3['cur_hex'] == 2348 and cm_0_3['on_board'] == 1 and cm_0_3['get_off_remain_time'] <= 0:
                        factionAI.task[ifv_3['obj_id']] = ['unload', RedOprs.TuJiDui_3.CM_0, False]
                    if ifv_3['cur_hex'] == 2348 and cm_0_3['get_off_remain_time'] > 0:
                        factionAI.task[ifv_3['obj_id']] = ['maneuver', 2744]
                    if ifv_3['cur_hex'] == 2744:
                        factionAI.task[ifv_3['obj_id']] = ['guard2nd', True]
            if ifv_4:
                cm_0_4 = myAgent.get_bop(RedOprs.TuJiDui_4.CM_0)
                infantry_4 = myAgent.get_bop(RedOprs.TuJiDui_4.Infantry)
                if cm_0_4:
                    if ifv_4['cur_hex'] == 1853 and cm_0_4['on_board'] == 1 and cm_0_4['get_off_remain_time'] <= 0:
                        factionAI.task[ifv_4['obj_id']] = ['unload', cm_0_4['obj_id'], False]
                if cm_0_4 and infantry_4:
                    if ifv_4['cur_hex'] == 1853 and cm_0_4['get_off_remain_time'] > 0:
                        factionAI.task[ifv_4['obj_id']] = ['deliver_safe', infantry_4['obj_id'], 3146]
                if infantry_4:
                    if infantry_4['get_off_remain_time'] > 0:
                        factionAI.task[ifv_4['obj_id']] = ['guard2nd', True]
            if ucv_1:
                if ucv_1['cur_hex'] == 1732:
                    factionAI.task[ucv_1['obj_id']] = ['maneuver', 2841]
                if ucv_1['cur_hex'] == 2841:
                    factionAI.task[ucv_1['obj_id']] = ['guard2nd', True]
            if ucv_2:
                if ucv_2['cur_hex'] == 2039:
                    factionAI.task[ucv_2['obj_id']] = ['maneuver', 2943]
                if ucv_2['cur_hex'] == 2943:
                    factionAI.task[ucv_2['obj_id']] = ['guard2nd', True]
            if ucv_3:
                if ucv_3['cur_hex'] == 2348:
                    factionAI.task[ucv_3['obj_id']] = ['maneuver', 3143]
                if ucv_3['cur_hex'] == 3143:
                    factionAI.task[ucv_3['obj_id']] = ['guard2nd', True]
            if ucv_4:
                if ucv_4['cur_hex'] == 1853:
                    factionAI.task[ucv_4['obj_id']] = ['maneuver', 2854]
                if ucv_4['cur_hex'] == 2854:
                    factionAI.task[ucv_4['obj_id']] = ['guard2nd', True]


class PaoBing:
    """炮兵组的策略实现"""

    def __init__(self, factionAI):
        self.members = factionAI.group_static['pao_bing']
        self.group_task = []
        self.tactic = None
        self.cur_step = 0

    def plan(self, factionAI, myAgent):
        self.cur_step = myAgent.observation['time']['cur_step']
        self.members = factionAI.group_info['pao_bing']['obj_ids']
        if not self.members:
            return

        # === 更新组任务 ===
        self.update_group_task(factionAI, myAgent)

        # === 分派算子任务 ===
        self.assign_opr_tasks(factionAI, myAgent)

    def update_group_task(self, factionAI, myAgent):
        """炮兵组任务"""
        """=== 清空炮兵组任务 ==="""
        # 清空条件：间瞄火力快要到达时
        flag_clean = False
        for jm_info in myAgent.observation['jm_points']:
            if jm_info['obj_id'] in self.members:
                if 100 < jm_info['fly_time'] < 150:
                    flag_clean = True
                    break
        if flag_clean:
            if self.group_task:
                if self.group_task[2]:
                    directing_opr_id = self.group_task[2]
                    if 'direct_artilleries' in factionAI.task.get(directing_opr_id, []):
                        factionAI.task.pop(directing_opr_id)
            self.group_task = []

        """=== 删除死亡的校射算子 ==="""
        if self.group_task:
            if isinstance(self.group_task[2], int):
                directing_opr_id = self.group_task[2]
                if not myAgent.get_bop(directing_opr_id):
                    self.group_task[2] = None

        """=== 关键点位压制任务 ==="""
        if self.new_plan_check(myAgent):
            if self.cur_step <= 150:
                self.group_task = ['FireSupport', 5348, None]  # 第1波压制
                return

        """=== 常规任务 ==="""
        if self.cur_step <= 150:
            return
        if not self.new_plan_check(myAgent):
            return
        # --- 计算飞行时间 ---
        fly_time = 0
        for jm_info in myAgent.observation['jm_points']:
            if jm_info['obj_id'] in self.members:
                if jm_info['fly_time'] > fly_time:
                    fly_time = jm_info['fly_time']
        if fly_time > 100:  # 快要达到的火力不打断
            return
        # --- 确定炮兵目标 ---
        being_jm_hexes = []  # 正在被间瞄损伤的六角格
        for jm_info in myAgent.observation['jm_points']:
            if jm_info['status'] == 0:  # 正在飞行
                being_jm_hexes.append(jm_info['pos'])
            elif jm_info['status'] == 1 and jm_info['boom_time'] < 150:
                being_jm_hexes.append(jm_info['pos'])
        being_jm_hexes = list(set(being_jm_hexes))
        being_planed_hexes = []  # 在计划中的间瞄目标
        if self.group_task:
            being_planed_hexes.append(self.group_task[1])
        near_by_hexes = []  # 我方算子周边格
        for opr in myAgent.get_our_oprs():
            if opr['type'] != BopType.Aircraft:
                opr_loc = opr['cur_hex']
                near_by_hexes.append(opr_loc)
        near_by_hexes = list(set(near_by_hexes))
        targets = self.get_artillery_target(myAgent)
        for key, value in targets.items():  # 过滤正在遭受打击的目标，以及列入间瞄任务的目标
            if not value:
                for tar_id in value:
                    tar_loc = myAgent.get_bop(tar_id)['cur_hex']
                    if tar_loc in being_jm_hexes + being_planed_hexes + near_by_hexes:
                        targets[key].remove(tar_id)
        # --- 有受压制的步兵时，优先打击此类目标 ---
        if targets[0]:
            target = targets[0][0]
            if self.group_task:
                cur_tar_loc = self.group_task[1]
                if check_keep_infantry_in_hex(cur_tar_loc, myAgent.situations.enemies):  # 当前的打击目标就是压制步兵
                    return
            tar_loc = myAgent.get_enemy(target)['cur_hex']
            self.group_task = ['FireSupport', tar_loc, None]
        elif not self.group_task:
            # === 没有受压制的步兵时，根据空闲的炮兵组，按照优先顺序分派jm任务 ===
            # 筛选炮击目标六角格
            candidate_targets = None
            flag_moving_infantry = False  # 选中的目标是否是机动中的步兵
            for m in range(1, 3):
                if targets[m]:
                    candidate_targets = targets[m]
                    if m == 1:
                        flag_moving_infantry = True
                    break
            if candidate_targets:
                arti_locs = []
                for arti_id in self.members:
                    arti = myAgent.get_bop(arti_id)
                    if arti:
                        arti_locs.append(arti['cur_hex'])
                # 视线中的目标格优先
                candidate_locs = [myAgent.get_enemy_info(obj_id)['operator']['cur_hex'] for obj_id in
                                  candidate_targets]
                candidate_locs = list(set(candidate_locs))
                candidate_locs_first, candidate_locs_others = [], []
                for hex in candidate_locs:  # 判断是否在视线中
                    for opr in myAgent.get_our_oprs():
                        if opr['sub_type'] in [BopType.Helicopter, BopType.CruiseMissile, BopType.Artillery]:
                            continue
                        opr_loc = opr['cur_hex']
                        opr_type = opr['type']
                        if opr_type == BopType.Aircraft:
                            sight_dist = 2
                            if myAgent.map.can_see(opr_loc, hex, 2) and cal_distance(opr_loc, hex) <= sight_dist:
                                candidate_locs_first.append(hex)
                                break
                        else:
                            sight_dist = 25
                            if myAgent.map.can_see(opr_loc, hex, 0) and cal_distance(opr_loc, hex) <= sight_dist:
                                candidate_locs_first.append(hex)
                                break
                candidate_locs_first = list(set(candidate_locs_first))
                candidate_locs_others = list(set(candidate_locs).difference(set(candidate_locs_first)))
                # 按照距离筛选同类目标
                if candidate_locs_first:
                    tar_loc = get_min_dist_loc(candidate_locs_first, arti_locs, myAgent)
                else:
                    tar_loc = get_min_dist_loc(candidate_locs_others, arti_locs, myAgent)
                # 对于机动中的步兵目标，做目标格预测
                if flag_moving_infantry:
                    nearest_city = min(myAgent.observation['cities'], key=lambda x: cal_distance(tar_loc, x['coord']))
                    tar_loc_predict = get_line(tar_loc, nearest_city['coord'])[0]
                    flag_opr_in_hex = False
                    for opr in myAgent.get_our_oprs():
                        if opr['type'] == BopType.Aircraft:
                            continue
                        if opr['cur_hex'] == tar_loc_predict:  # 保证不会误伤友军
                            flag_opr_in_hex = True
                    if not flag_opr_in_hex:
                        tar_loc = tar_loc_predict
                self.group_task = ['FireSupport', tar_loc, None]
            else:  # 无目标可选，不分派任务
                self.group_task = []

        """=== 为炮兵组任务分配校射算子 ==="""
        if self.cur_step > 150:
            if not self.group_task:
                return
            if isinstance(self.group_task[2], int):
                return
            fly_time = 0
            for jm_info in myAgent.observation['jm_points']:
                if jm_info['obj_id'] in self.members:
                    if jm_info['fly_time'] > fly_time:
                        fly_time = jm_info['fly_time']
            if fly_time > 50:  # 只为早期任务分配校射算子
                return
            # 筛选校射算子
            tar_loc = self.group_task[1]
            directing_opr_id = self.get_directing_opr(tar_loc, myAgent)
            if directing_opr_id:
                self.group_task[2] = directing_opr_id  # 完善炮兵组任务
                group_name = get_group_by_obj_id(directing_opr_id, factionAI)
                if group_name:
                    factionAI.group_info[group_name]['obj_ids'].remove(directing_opr_id)  # 暂时将校射算子调离原分组
                    if 'direct_artilleries' not in factionAI.task.get(directing_opr_id, [])\
                            and factionAI.task.get(directing_opr_id)\
                            and not factionAI.task_backup.get(directing_opr_id):
                        factionAI.task_backup[directing_opr_id] = factionAI.task.pop(directing_opr_id)

    def assign_opr_tasks(self, factionAI, myAgent):
        """为算子分派任务"""
        """=== 清除完成的校射任务 ==="""
        if self.group_task:
            if isinstance(self.group_task[2], int):
                directing_opr_ids = [self.group_task[2]]
            else:
                directing_opr_ids = []
            for opr in myAgent.get_our_oprs():
                obj_id = opr['obj_id']
                if obj_id not in factionAI.task.keys():
                    continue
                if 'direct_artilleries' in factionAI.task[obj_id]:
                    if not directing_opr_ids:
                        factionAI.task.pop(obj_id)
                    else:
                        if obj_id not in directing_opr_ids:
                            factionAI.task.pop(obj_id)

        """=== 分派炮兵齐射任务 ==="""
        if self.group_task:
            for arti_id in self.members:
                factionAI.task[arti_id] = ['jm_salvo', self.group_task[1]]

        """=== 分派校射任务 ==="""
        if self.group_task:
            if isinstance(self.group_task[2], int):
                directing_opr_id = self.group_task[2]
                if directing_opr_id in factionAI.task.keys():
                    if factionAI.task[directing_opr_id] == ['direct_artilleries', self.group_task[1]]:
                        return
                factionAI.task[directing_opr_id] = ['direct_artilleries', self.group_task[1]]

    def get_directing_opr(self, tar_loc, myAgent):
        """为炮兵组选择校射算子（红方不使用无人机校射）"""
        # === 确定对tar_enemies的可视半径 ===
        observe_radius = {}  # 最小可观察范围
        observe_radius[BopType.Infantry] = 25
        observe_radius[BopType.Vehicle] = 25
        observe_radius[BopType.Drone] = 2
        # === 计算对tar_enemies的最小观察区域 ===
        dist_matrix = myAgent.my_map['dist_matrix']
        map_size = myAgent.get_map_size()
        n_hexes = map_size['row'] * map_size['col']
        tar_loc_ind = mapid_2_matind(tar_loc, myAgent.get_map_size())
        dist_vec = dist_matrix[tar_loc_ind]
        see_vec_ground = myAgent.my_map['see_matrix'][0][tar_loc_ind]
        see_vec_air = myAgent.my_map['see_matrix'][2][:, tar_loc_ind]
        observe_area = {}
        for bop_type, radius in observe_radius.items():
            if bop_type in [BopType.Infantry, BopType.Vehicle]:
                observe_area[bop_type] = np.arange(n_hexes)[dist_vec <= observe_radius[bop_type] & see_vec_ground]
            else:
                observe_area[bop_type] = np.arange(n_hexes)[dist_vec <= observe_radius[bop_type] & see_vec_air]
        # === 筛选出能够在150s内抵达最小观察区域的我方算子 ===
        cost_matrix_infantry = myAgent.my_map['cost_matrix'][1]
        # cost_matrix_little_aircraft = dist_matrix * 7.2
        candidate_infantries, min_costs_infantries = [], {}
        # candidate_drones, candidate_infantries, min_costs_drones, min_costs_infantries = [], [], {}, {}
        for opr in myAgent.get_our_oprs():
            if opr['sub_type'] in [BopType.Artillery, BopType.CruiseMissile, BopType.Helicopter]:
                continue
            opr_loc_ind = mapid_2_matind(opr['cur_hex'], map_size)
            if opr['type'] == BopType.Infantry:
                cost_vec = cost_matrix_infantry[opr_loc_ind]
                min_cost_infantry = np.min(cost_vec[observe_area[BopType.Infantry]])
                if min_cost_infantry < 150:
                    candidate_infantries.append(opr)
                    min_costs_infantries[opr['obj_id']] = min_cost_infantry
            # elif opr['sub_type'] == BopType.Drone:
            #     cost_vec = cost_matrix_little_aircraft[opr_loc_ind]
            #     min_cost_drone = np.min(cost_vec[observe_area[BopType.Drone]])
            #     if min_cost_drone < 150:
            #         candidate_drones.append(opr)
            #         min_costs_drones[opr['obj_id']] = min_cost_drone
        # 进一步从candidate_oprs筛选出最优的校射算子
        if not candidate_infantries:  # not candidate_drones
            return None
        tar_enemies = []
        for enemy_info in myAgent.situations.enemies:
            if enemy_info['spread_flag']:
                enemy = enemy_info['operator']
                ene_loc = enemy['cur_hex']
                if enemy['type'] != BopType.Aircraft:
                    if enemy['armor'] != ArmorType.Composit:
                        if ene_loc == tar_loc:
                            if np.max(enemy_info['pos_probs']) > 1 - 1e-8:
                                tar_enemies.append(enemy)
        if not tar_enemies:
            return None
        if candidate_infantries:
            best_opr = min(candidate_infantries, key=lambda opr: min_costs_infantries[opr['obj_id']])
            best_opr_id = best_opr['obj_id']
            return best_opr_id
        # elif candidate_drones:
        #     best_opr = min(candidate_drones, key=lambda opr: min_costs_drones[opr['obj_id']])
        #     best_opr_id = best_opr['obj_id']
        #     return best_opr_id

    def new_plan_check(self, myAgent):
        for anti_id in self.members:
            if myAgent.observation['valid_actions'].get(anti_id):
                if not myAgent.observation['valid_actions'][anti_id].get(ActionType.JMPlan):
                    return False
        return True

    def get_artillery_target(self, myAgent):
        """确定炮兵的目标"""
        targets = {0: [], 1: [], 2: []}  # 按级别区分的目标
        # 0：压制的步兵
        # 1：机动中的步兵
        # 2：夺控点旁边的敌方单位
        # 区分目标等级
        for enemy_info in myAgent.situations.enemies:
            if not enemy_info['spread_flag']:
                continue
            enemy = enemy_info['operator']
            ene_id = enemy['obj_id']
            ene_loc = enemy['cur_hex']
            if enemy['type'] == BopType.Aircraft:
                continue
            if enemy['armor'] == ArmorType.Composit:
                continue
            if np.max(enemy_info['pos_probs']) < 1 - 1e-8:
                continue
            if enemy['type'] == BopType.Infantry and enemy['keep'] == 1:
                targets[0].append(ene_id)  # 最高等级是压制的步兵
            elif enemy['type'] == BopType.Infantry and enemy['speed'] > 0:
                targets[1].append(ene_id)
            elif min([cal_distance(ene_loc, city['coord']) for city in myAgent.observation['cities']]) <= 1:
                targets[2].append(ene_id)  # 第三是夺控点周边的算子（非坦克）
        return targets


class ZhiShengJi:
    """直升机组的策略实现"""

    def __init__(self, factionAI):
        self.members = factionAI.group_static['zhi_sheng_ji']
        self.group_task = ['Deployment']
        self.tactic = []
        self.cur_step = 0

    def plan(self, factionAI, myAgent):
        self.cur_step = myAgent.observation['time']['cur_step']
        self.members = factionAI.group_info['zhi_sheng_ji']['obj_ids']
        if not self.members:
            return

        # === 更新组任务 ===
        self.update_group_task(factionAI, myAgent)

        # === 分派算子任务 ===
        self.assign_opr_tasks(factionAI, myAgent)

    def update_group_task(self, factionAI, myAgent):
        # # --- 部署任务 ---
        if not self.group_task and self.cur_step < 10:
            self.group_task = ['Deployment']
        # # --- 火力输出任务 ---
        if 'Deployment' in self.group_task:
            n_enemy_infantries, n_enemy_tanks = 0, 0
            for enemy_info in myAgent.situations.enemies:
                enemy = enemy_info['operator']
                if enemy['type'] == BopType.Infantry:
                    n_enemy_infantries += 1
                elif enemy['sub_type'] == BopType.Tank:
                    n_enemy_tanks += 1
            n_our_tanks = len(factionAI.group_info['tan_ke']['obj_ids'])
            if n_enemy_infantries <= 3 or (n_enemy_tanks - n_our_tanks) <= 1:
                self.group_task = ['OutputFire']

    def assign_opr_tasks(self, factionAI, myAgent):
        heli_0 = myAgent.get_bop(RedOprs.ZhiShengJi.Helicopter_0)
        heli_1 = myAgent.get_bop(RedOprs.ZhiShengJi.Helicopter_1)
        if 'Deployment' in self.group_task:
            if heli_0:
                if heli_0['cur_hex'] == 838:
                    factionAI.task[heli_0['obj_id']] = ['maneuver', 4535]  # +++
                if heli_1['cur_hex'] == 4535:
                    factionAI.task[heli_0['obj_id']] = ['guard2nd', False]  # +++
                if heli_1['cur_hex'] > 3500 and self.cur_step > 350:
                    factionAI.task[heli_0['obj_id']] = ['rapid_march', 2649, 2]  # +++
                if heli_0['cur_hex'] == 2649:
                    factionAI.task[heli_0['obj_id']] = ['guard2nd', False]  # +++
            if heli_1:
                if heli_1['cur_hex'] == 1137:
                    factionAI.task[heli_1['obj_id']] = ['maneuver', 4536]  # +++
                if heli_1['cur_hex'] == 4536:
                    factionAI.task[heli_1['obj_id']] = ['guard2nd', False]  # +++
                if heli_1['cur_hex'] > 3500 and self.cur_step > 350:
                    factionAI.task[heli_1['obj_id']] = ['rapid_march', 2641, 2]  # +++
                if heli_1['cur_hex'] == 2641:
                    factionAI.task[heli_1['obj_id']] = ['guard2nd', False]  # +++

                # if heli_1['cur_hex'] != 4047:
                #     # 根据是否有战车，确定转移攻击点位
                #     tar_area = get_range(5146, 1, myAgent.get_map_size()) + get_range(5049, 1, myAgent.get_map_size()) \
                #                + get_range(5052, 1, myAgent.get_map_size())
                #     tar_area = list(set(tar_area))
                #     flag_transfer = False
                #     for enemy_info in myAgent.situations.enemies:
                #         if not enemy_info['spread_flag']:
                #             continue
                #         enemy = enemy_info['operator']
                #         if enemy['sub_type'] == BopType.IFV and enemy['cur_hex'] in tar_area:
                #             flag_transfer = True
                #             break
                #     flag_infantry = False
                #     for enemy_info in myAgent.situations.enemies:
                #         if not enemy_info['spread_flag']:
                #             continue
                #         enemy = enemy_info['operator']
                #         if enemy['type'] == BopType.Infantry and enemy['on_board'] == 0:
                #             if cal_distance(enemy['cur_hex'], 4047) <= 10:
                #                 flag_infantry = True
                #                 break
                #         if enemy['sub_type'] == BopType.IFV and enemy['get_off_partner_id']:
                #             if cal_distance(enemy['cur_hex'], 4047) > 10:
                #                 continue
                #             for passenger_id in enemy['get_off_partner_id']:
                #                 passenger = myAgent.get_enemy(passenger_id)
                #                 if passenger['type'] == BopType.Infantry:
                #                     flag_infantry = True
                #                     break
                #     if flag_transfer and not flag_infantry:
                #         factionAI.task[heli_1['obj_id']] = ['ambush', 4047]  # +++
        elif 'OutputFire' in self.group_task:
            if heli_0:
                factionAI.task[heli_0['obj_id']] = ['fire_propulsion']  # +++
            if heli_1:
                factionAI.task[heli_1['obj_id']] = ['fire_propulsion']  # +++

        # 弹药耗尽后的处理
        if heli_0:
            if sum(heli_0['remain_bullet_nums'].values()) <= 0:
                factionAI.task[heli_0['obj_id']] = ['rapid_march', 925, 2]  # +++
        if heli_1:
            if sum(heli_1['remain_bullet_nums'].values()) <= 0:
                factionAI.task[heli_1['obj_id']] = ['rapid_march', 926, 2]  # +++


class WuRenJi:
    """无人机组的策略实现"""

    def __init__(self, factionAI):
        self.members = factionAI.group_static['wu_ren_ji']
        self.group_task = ['Deployment']
        self.tactic = []
        self.cur_step = 0

    def plan(self, factionAI, myAgent):
        self.cur_step = myAgent.observation['time']['cur_step']
        self.members = factionAI.group_info['wu_ren_ji']['obj_ids']
        if not self.members:
            return

        # === 更新组任务 ===
        self.update_group_task()

        # === 分派算子任务 ===
        self.assign_opr_tasks(factionAI, myAgent)

    def update_group_task(self):
        # --- 部署任务 ---
        if not self.group_task and self.cur_step < 10:
            self.group_task = ['Deployment']
        # --- 火力输出任务 ---
        if self.cur_step > 300:
            self.group_task = ['GuideFire']

    def assign_opr_tasks(self, factionAI, myAgent):
        drone_0 = myAgent.get_bop(RedOprs.WuRenJi.Drone_0)
        drone_1 = myAgent.get_bop(RedOprs.WuRenJi.Drone_1)
        if 'Deployment' in self.group_task:
            if drone_0:
                if drone_0['cur_hex'] == 839:
                    factionAI.task[drone_0['obj_id']] = ['ambush3rd', 4651, False]  # +++
                if drone_1['cur_hex'] == 1138:
                    factionAI.task[drone_1['obj_id']] = ['ambush3rd', 4648, False]  # +++
        elif 'GuideFire' in self.group_task:
            if drone_0:
                factionAI.task[drone_0['obj_id']] = ['guide_fire']  # +++
            if drone_1:
                factionAI.task[drone_1['obj_id']] = ['guide_fire']  # +++


class XunFeiDan:
    """巡飞弹组的策略实现"""

    def __init__(self, factionAI):
        self.members = factionAI.group_static['xun_fei_dan']
        self.group_task = ['Deployment']
        self.tactic = []
        self.cur_step = 0

    def plan(self, factionAI, myAgent):
        self.cur_step = myAgent.observation['time']['cur_step']
        self.members = factionAI.group_info['xun_fei_dan']['obj_ids']
        if not self.members:
            return

        # 分派巡飞弹任务
        self.assign_opr_tasks(factionAI, myAgent)

        # 计划巡飞弹的发射任务
        # self.plan_launch_cm(factionAI, myAgent)

    def assign_opr_tasks(self, factionAI, myAgent):
        # === 为当前巡飞弹组的算子分派任务 ===
        for cm_id in self.members:
            cm = myAgent.get_bop(cm_id)
            if cm:
                # if cm_id in [RedOprs.TuJiDui_2.CM_0, RedOprs.TuJiDui_3.CM_0, RedOprs.TuJiDui_4.CM_0]:
                #     if cm['on_board'] == 0:
                #         patrol_area = [5247, 5049, 4951, 4953, 4846, 4748, 4649, 4653]
                #         if cm_id == RedOprs.TuJiDui_2.CM_0:
                #             if cm['cur_hex'] == 2039:
                #                 factionAI.task[cm_id] = ['maneuver', 5247]
                #             elif cm['cur_hex'] == 5247 and self.cur_step <= 650:
                #                 factionAI.task[cm_id] = ['cruise_missile_patrol', patrol_area]
                #         if cm_id == RedOprs.TuJiDui_3.CM_0:
                #             if cm['cur_hex'] == 2348:
                #                 factionAI.task[cm_id] = ['maneuver', 5049]
                #             elif cm['cur_hex'] == 5049 and self.cur_step <= 650:
                #                 factionAI.task[cm_id] = ['cruise_missile_patrol', patrol_area]
                #         if cm_id == RedOprs.TuJiDui_4.CM_0:
                #             if cm['cur_hex'] == 1853:
                #                 factionAI.task[cm_id] = ['maneuver', 4952]
                #             elif cm['cur_hex'] == 4952 and self.cur_step <= 650:
                #                 factionAI.task[cm_id] = ['cruise_missile_patrol', patrol_area]
                #         if self.cur_step > 650:
                #             factionAI.task[cm_id] = ['cruise_missile_2nd']
                # else:
                if cm['on_board'] == 0:
                    factionAI.task[cm_id] = ['cruise_missile_2nd']

        # 制定巡飞弹射击的计划
        infantries_round_city, getoff_ifvs = [], []
        for enemy_info in myAgent.situations.enemies:
            if not enemy_info['spread_flag']:
                continue
            enemy = enemy_info['operator']
            if enemy['type'] == BopType.Infantry and enemy['on_board'] == 0:
                min_dist = min(
                    [cal_distance(enemy['cur_hex'], city['coord']) for city in myAgent.observation['cities']])
                if min_dist > 1:
                    continue
                if enemy['keep'] == 0:
                    infantries_round_city.append(enemy)
            elif enemy['sub_type'] == BopType.IFV and enemy['get_off_partner_id']:
                if np.max(enemy_info['pos_probs']) < 1 - 1e-8:
                    continue
                flag_infantry = False
                for passenger_id in enemy['get_off_partner_id']:
                    passenger = myAgent.get_enemy(passenger_id)
                    if not passenger:
                        continue
                    if passenger['type'] == BopType.Infantry:
                        flag_infantry = True
                        break
                if not flag_infantry:
                    continue
                getoff_ifvs.append(enemy)
        if not infantries_round_city and not getoff_ifvs:
            return
        tar_inf, cm_id_chosen = None, None
        if infantries_round_city:
            min_dist = 1000
            for inf in infantries_round_city:
                for cm_id in self.members:
                    cm = myAgent.get_bop(cm_id)
                    if not cm:
                        continue
                    cur_dist = cal_distance(cm['cur_hex'], inf['cur_hex'])
                    if min_dist > cur_dist:
                        min_dist = cur_dist
                        tar_inf, cm_id_chosen = inf, cm_id
        if tar_inf and cm_id_chosen:
            factionAI.task[cm_id_chosen] = ['targeted_kill', tar_inf['obj_id']]
        else:
            # 攻击下车中的战车
            tar_ifv, cm_id_chosen = None, None
            if getoff_ifvs:
                min_dist = 1000
                for ifv in getoff_ifvs:
                    for cm_id in self.members:
                        cm = myAgent.get_bop(cm_id)
                        if not cm:
                            continue
                        cur_dist = cal_distance(cm['cur_hex'], ifv['cur_hex'])
                        if min_dist > cur_dist:
                            min_dist = cur_dist
                            tar_ifv, cm_id_chosen = ifv, cm_id
            if tar_ifv and cm_id_chosen:
                factionAI.task[cm_id_chosen] = ['targeted_kill', tar_ifv['obj_id']]


def check_keep_infantry_in_hex(loc, enemies_info):
    """判断六角格loc中是否有压制状态的步兵，用于炮兵目标选择"""
    for enemy_info in enemies_info:
        if enemy_info['spread_flag']:
            enemy = enemy_info['operator']
            if enemy['cur_hex'] == loc:
                if enemy['keep'] == 1:
                    if enemy['type'] == BopType.Infantry:
                        return True
    return False


def get_min_dist_loc(candidate_locs, refer_locs, myAgent):
    """根据距离refer_locs远近从candidate_locs中筛选六角格"""
    map_size = myAgent.get_map_size()
    candidate_locs_ind = mapid_2_matind(np.array(candidate_locs), map_size)
    refer_locs_ind = mapid_2_matind(np.array(refer_locs), map_size)
    dist_matrix = myAgent.my_map['dist_matrix'][candidate_locs_ind.reshape(-1, 1), refer_locs_ind.reshape(1, -1)]
    avg_dist_vec = np.mean(dist_matrix, axis=1)
    best_ind = np.argmin(avg_dist_vec)
    return candidate_locs[best_ind]


def get_group_by_obj_id(obj_id, factionAI):
    for group_name, info in factionAI.group_info.items():
        if obj_id in info['obj_ids']:
            return group_name
    return None


def get_init_group_by_obj_id(obj_id, factionAI):
    for group_name, info in factionAI.group_static.items():
        if obj_id in info:
            return group_name
    return None
